﻿/**
 * File:   gesture_lock.c
 * Author: AWTK Develop Team
 * Brief:  九宫格手势密码控件。
 *
 * Copyright (c) 2021 - 2021 Guangzhou ZHIYUAN Electronics Co.,Ltd.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * License file for more details.
 *
 */

/**
 * History:
 * ================================================================
 * 2021-02-20 Zhang Zhongji <zhangzhongji@zlg.cn> created
 *
 */


#include "tkc/mem.h"
#include "tkc/utils.h"
#include "gesture_lock.h"

static ret_t gesture_lock_get_prop(widget_t* widget, const char* name, value_t* v) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);
  ret_t ret = RET_NOT_FOUND;

// log_debug("%s(%s)\n", __func__, name);
  if (tk_str_eq(name, GESTURE_LOCK_PROP_PASSWORD) || tk_str_eq(name, WIDGET_PROP_VALUE)) {
    value_set_str(v, gesture_lock->password.str);
    ret = RET_OK;
  } else if(tk_str_eq(name, GESTURE_LOCK_PROP_SHOW_GESTURE)) {
    value_set_bool(v, gesture_lock->show_gesture);
    ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P0_X)) {
      value_set_int32(v, gesture_lock->base_points[0].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P0_Y)) {
      value_set_int32(v, gesture_lock->base_points[0].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P1_X)) {
      value_set_int32(v, gesture_lock->base_points[1].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P1_Y)) {
      value_set_int32(v, gesture_lock->base_points[1].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P2_X)) {
      value_set_int32(v, gesture_lock->base_points[2].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P2_Y)) {
      value_set_int32(v, gesture_lock->base_points[2].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P3_X)) {
      value_set_int32(v, gesture_lock->base_points[3].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P3_Y)) {
      value_set_int32(v, gesture_lock->base_points[3].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P4_X)) {
      value_set_int32(v, gesture_lock->base_points[4].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P4_Y)) {
      value_set_int32(v, gesture_lock->base_points[4].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P5_X)) {
      value_set_int32(v, gesture_lock->base_points[5].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P5_Y)) {
      value_set_int32(v, gesture_lock->base_points[5].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P6_X)) {
      value_set_int32(v, gesture_lock->base_points[6].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P6_Y)) {
      value_set_int32(v, gesture_lock->base_points[6].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P7_X)) {
      value_set_int32(v, gesture_lock->base_points[7].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P7_Y)) {
      value_set_int32(v, gesture_lock->base_points[7].y);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P8_X)) {
      value_set_int32(v, gesture_lock->base_points[8].x);
      ret = RET_OK;
  } else if (tk_str_eq(name, GESTURE_LOCK_PROP_P8_Y)) {
      value_set_int32(v, gesture_lock->base_points[8].y);
      ret = RET_OK;
  }

  return ret;
}

static ret_t gesture_lock_set_prop(widget_t* widget, const char* name, const value_t* v) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);
  ret_t ret = RET_NOT_FOUND;

//  log_debug("%s(%s)\n", __func__, name);
  if (tk_str_eq(name, GESTURE_LOCK_PROP_SHOW_GESTURE)) {
    gesture_lock->show_gesture = value_bool(v);
    ret = RET_OK;
  }

  return ret;
}

static ret_t gesture_lock_on_destroy(widget_t* widget) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(widget != NULL && gesture_lock != NULL, RET_BAD_PARAMS);

  timer_remove(gesture_lock->timer_id);
  darray_destroy(gesture_lock->input_points);
  str_reset(&gesture_lock->password);

  return RET_OK;
}

// 如果划过的点经过固定基点，则返固定点索引，否则返回-1
static int8_t gesture_lock_point_in_block(gesture_lock_t* gesture_lock, xy_t x, xy_t y) {
  return_value_if_fail(gesture_lock != NULL, -1);

  for (int i = 0; i < 9; i++) {
    int32_t delta_x = tk_abs(gesture_lock->base_points[i].x - x);
    int32_t delta_y = tk_abs(gesture_lock->base_points[i].y - y);
    int32_t distance_between_base_point = delta_x * delta_x + delta_y * delta_y;
    if (gesture_lock->r * gesture_lock->r >= distance_between_base_point) {
      return i;
    }
  }

  return -1;
}

static ret_t gesture_lock_draw_lines_to_current_point(gesture_lock_t* gesture_lock, vgcanvas_t* vg, color_t color) {
  return_value_if_fail(gesture_lock != NULL, RET_BAD_PARAMS);

  vgcanvas_save(vg);
  if (gesture_lock->input_points->size > 0) {
    int input_point_index = 0;
    point_t *begin = darray_get(gesture_lock->input_points, input_point_index++);
    vgcanvas_set_stroke_color(vg, color);
    while (gesture_lock->input_points->size > input_point_index) {
      point_t *end = darray_get(gesture_lock->input_points, input_point_index++);

      if (begin != NULL && end != NULL) {
        vgcanvas_begin_path(vg);
        vgcanvas_move_to(vg, begin->x, begin->y);
        vgcanvas_line_to(vg, end->x, end->y);
        vgcanvas_stroke(vg);
        begin = end;
      }
    }

    if (begin != NULL && gesture_lock->pressed == TRUE) {
      vgcanvas_begin_path(vg);
      vgcanvas_move_to(vg, begin->x, begin->y);
      vgcanvas_line_to(vg, gesture_lock->current_position.x, gesture_lock->current_position.y);
      vgcanvas_stroke(vg);
    }
  }

  vgcanvas_restore(vg);
  vgcanvas_flush(vg);

  return RET_OK;
}

static ret_t gesture_lock_draw_block(gesture_lock_t* gesture_lock, vgcanvas_t* vg, float_t x, float_t y, float_t r) {
  return_value_if_fail(vg != NULL, RET_BAD_PARAMS);

  vgcanvas_begin_path(vg);
  //画内圆
  vgcanvas_arc(vg, x, y, r/3.0, 0, 2 * 3.15, FALSE);
  vgcanvas_fill(vg);

  vgcanvas_begin_path(vg);
  //画外圆
  vgcanvas_arc(vg, x, y, r, 0, 2 * 3.15, FALSE);
  vgcanvas_stroke(vg);

  return RET_OK;
}

static ret_t gesture_lock_on_paint_self(widget_t* widget, canvas_t* c) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  style_t* style = widget->astyle;
  color_t fg, selected_fg, color;
  int32_t margin;
  int32_t x_offset, y_offset;
  xy_t x, y;

  fg = color_init(0x21, 0x9a, 0xff, 0xff);
  selected_fg = color_init(0x52, 0x49, 0xe7, 0xff);
  margin = 10;
  if (style != NULL) {
      fg = style_get_color(style, STYLE_ID_FG_COLOR, fg);
      selected_fg = style_get_color(style, STYLE_ID_SELECTED_FG_COLOR, selected_fg);
      margin = style_get_int(style, STYLE_ID_MARGIN, margin);
  }

  vgcanvas_t* vg = canvas_get_vgcanvas(c);
  vgcanvas_save(vg);

  if (gesture_lock->pressed == TRUE) {
      color = selected_fg;
  } else {
      color = fg;
  }

  vgcanvas_set_fill_color(vg, color);
  vgcanvas_set_stroke_color(vg, color);

  wh_t r = (tk_min(widget->w, widget->h) - (2 * margin)) / 8;
  wh_t real_w = 8 * r;
  wh_t real_h = real_w;

  // 居中偏置，canvas的x、y轴的偏移为ox、oy
  x_offset = c->ox + (widget->w - real_w)/2;
  y_offset = c->oy + (widget->h - real_h)/2;

  int k = 0;
  for (int dy = 0; dy < 3; dy++) {
    for (int dx = 0; dx < 3; dx++) {
        x = r + dx*3*r + x_offset;
        y = r + dy*3*r + y_offset;
        gesture_lock_draw_block(gesture_lock, vg, x, y, r);

        // 更新控件信息
        gesture_lock->base_points[k].x = x;
        gesture_lock->base_points[k].y = y;
        k++;
    }
  }

    gesture_lock->r = r;

    // 画路径到当前点
    if (gesture_lock->show_gesture) {
      color_t line_color = color_init(fg.rgba.r, fg.rgba.g, fg.rgba.b, fg.rgba.a + 100);
      gesture_lock_draw_lines_to_current_point(gesture_lock, vg, line_color);
    }

  vgcanvas_restore(vg);

  return RET_OK;
}

static inline ret_t on_timer(const timer_info_t* timer) {
  widget_t* widget = WIDGET(timer->ctx);
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);

  if (gesture_lock->pressed == FALSE) {
    darray_clear(gesture_lock->input_points);
  }

  widget_invalidate(widget, NULL);
  timer_suspend(timer->id);

  return RET_REPEAT;
}

static ret_t gesture_lock_on_pointer_down(widget_t* widget, pointer_event_t* evt) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL && evt != NULL, RET_BAD_PARAMS);

  darray_clear(gesture_lock->input_points);
  str_clear(&gesture_lock->password);
  gesture_lock->current_position.x = evt->x;
  gesture_lock->current_position.y = evt->y;
  gesture_lock->pressed = TRUE;

  int8_t i = gesture_lock_point_in_block(gesture_lock, gesture_lock->current_position.x, gesture_lock->current_position.y);
  if (i >= 0) {
    darray_push(gesture_lock->input_points, &gesture_lock->base_points[i]);
    str_append_int(&gesture_lock->password, i);
  }

  return RET_OK;
}

static ret_t gesture_lock_on_pointer_move(widget_t* widget, pointer_event_t* evt) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL && evt != NULL, RET_BAD_PARAMS);

  gesture_lock->current_position.x = evt->x;
  gesture_lock->current_position.y = evt->y;

  //保存点，并绘制划过路径
  int8_t i = gesture_lock_point_in_block(gesture_lock, gesture_lock->current_position.x, gesture_lock->current_position.y);
  if (i >= 0) {
    if (darray_find(gesture_lock->input_points, &gesture_lock->base_points[i]) == NULL) {
      darray_push(gesture_lock->input_points, &gesture_lock->base_points[i]);
      str_append_int(&gesture_lock->password, i);
    }
  }

  return RET_OK;
}

static ret_t gesture_lock_on_pointer_up(widget_t* widget, pointer_event_t* evt) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL && evt != NULL, RET_BAD_PARAMS);

  if (gesture_lock->password.size > 0) {
    value_change_event_t vce;
    prop_change_event_t pce;
    value_t value;

    value_set_str(&value, gesture_lock->password.str);
    prop_change_event_init(&pce, EVT_PROP_CHANGED, GESTURE_LOCK_PROP_PASSWORD, &value);
    widget_dispatch(widget, (event_t*)&pce);

    value_change_event_init(&vce, EVT_VALUE_CHANGED, widget);
    value_set_str(&vce.old_value, gesture_lock->password.str);
    value_set_str(&vce.new_value, gesture_lock->password.str);
    widget_dispatch(widget, (event_t*)&vce);

//    log_debug("gesture_lock: EVT_VALUE_CHANGED: %s\n", gesture_lock->password);
  }

  timer_resume(gesture_lock->timer_id);

  return RET_OK;
}

static ret_t gesture_lock_pointer_up_cleanup(widget_t* widget) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL, RET_BAD_PARAMS);
  widget_ungrab(widget->parent, widget);
  gesture_lock->pressed = FALSE;

  return RET_OK;
}

static ret_t gesture_lock_on_event(widget_t* widget, event_t* e) {
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(widget != NULL && gesture_lock != NULL, RET_BAD_PARAMS);

  //log_debug("e->type=%d\n", e->type);
  switch (e->type) {
    case EVT_POINTER_DOWN: {
      gesture_lock_on_pointer_down(widget, (pointer_event_t*)e);
      widget_grab(widget->parent, widget);
      break;
    }
    case EVT_POINTER_DOWN_ABORT: {
      gesture_lock_pointer_up_cleanup(widget);
      break;
    }
    case EVT_POINTER_MOVE: {
      if (gesture_lock->pressed == TRUE) {
        gesture_lock_on_pointer_move(widget, (pointer_event_t*)e);
        widget_invalidate(widget, NULL);
      }
      break;
    }
    case EVT_POINTER_UP: {
      gesture_lock_on_pointer_up(widget, (pointer_event_t*)e);
      gesture_lock_pointer_up_cleanup(widget);
      widget_invalidate(widget, NULL);
      break;
    }
    default:
      break;
  }

  return RET_OK;
}

const char* s_gesture_lock_properties[] = {
  GESTURE_LOCK_PROP_PASSWORD,
  GESTURE_LOCK_PROP_SHOW_GESTURE,
  GESTURE_LOCK_PROP_P0_X,
  GESTURE_LOCK_PROP_P0_Y,
  GESTURE_LOCK_PROP_P1_X,
  GESTURE_LOCK_PROP_P1_Y,
  GESTURE_LOCK_PROP_P2_X,
  GESTURE_LOCK_PROP_P2_Y,
  GESTURE_LOCK_PROP_P3_X,
  GESTURE_LOCK_PROP_P3_Y,
  GESTURE_LOCK_PROP_P4_X,
  GESTURE_LOCK_PROP_P4_Y,
  GESTURE_LOCK_PROP_P5_X,
  GESTURE_LOCK_PROP_P5_Y,
  GESTURE_LOCK_PROP_P6_X,
  GESTURE_LOCK_PROP_P6_Y,
  GESTURE_LOCK_PROP_P7_X,
  GESTURE_LOCK_PROP_P7_Y,
  GESTURE_LOCK_PROP_P8_X,
  GESTURE_LOCK_PROP_P8_Y,
  NULL
};

TK_DECL_VTABLE(gesture_lock) = {.size = sizeof(gesture_lock_t),
                          .type = WIDGET_TYPE_GESTURE_LOCK,
                          .clone_properties = s_gesture_lock_properties,
                          .persistent_properties = s_gesture_lock_properties,
                          .parent = TK_PARENT_VTABLE(widget),
                          .create = gesture_lock_create,
                          .on_paint_self = gesture_lock_on_paint_self,
                          .set_prop = gesture_lock_set_prop,
                          .get_prop = gesture_lock_get_prop,
                          .on_event = gesture_lock_on_event,
                          .on_destroy = gesture_lock_on_destroy};

widget_t* gesture_lock_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h) {
  widget_t* widget = widget_create(parent, TK_REF_VTABLE(gesture_lock), x, y, w, h);
  gesture_lock_t* gesture_lock = GESTURE_LOCK(widget);
  return_value_if_fail(gesture_lock != NULL, NULL);

  gesture_lock->show_gesture = TRUE;
  gesture_lock->pressed = FALSE;
  gesture_lock->input_points = darray_create(9, NULL, NULL);
  str_init(&gesture_lock->password, 9);

  gesture_lock->timer_id = timer_add(on_timer, gesture_lock, 500);
  if (gesture_lock->timer_id == TK_INVALID_ID) {
      log_error("gesture lock: add timer error!\n");
  } else {
      timer_suspend(gesture_lock->timer_id);
  }

  return widget;
}

widget_t* gesture_lock_cast(widget_t* widget) {
  return_value_if_fail(WIDGET_IS_INSTANCE_OF(widget, gesture_lock), NULL);

  return widget;
}
